package roborally.program.command;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import roborally.itemsonboard.Robot;
import roborally.program.text.TextManipulation;
import be.kuleuven.cs.som.annotate.Basic;
import be.kuleuven.cs.som.annotate.Immutable;
import be.kuleuven.cs.som.annotate.Raw;

/**
 * A class representing a conditional statement.
 * It consists of a condition and two commands. 
 * The first is executed if the condition is true, the other if the condition is false.
 * 
 * @invar (repeated)
 * 		The interal commands of this IfCommand must be valid.
 * 		| hasProperInteralCommands()
 * @invar (repeated)
 * 		The condition of this conditionalCommand must be valid.
 * 		| isValidCondition(this.getCondition())
 * @invar The internal false commands of this IfCommand must be valid.
 * 
 * @author Molderez Tom, 2de bach Ingenieurswetenschappen Computerwetenschappen-Elektrotechniek and Reniers Jorn, 2de bach Ingenieurswetenschappen Werktuigkunde-Computerwetenschappen
 * @version 1.2
 *
 */
public class IfCommand extends ConditionalCommand {

	/**
	 * Creates a new IfCommand.
	 * 
	 * @param condition The condition of this IfCommand.
	 * @param commands The interal commands of this IfCommands.
	 * @param falseCommands The internal false commands of this IfCommand
	 * @post The condition of this will be equal to the given condition.
	 * @post For every command in commands, according the order, this new will contain the specific command.
	 * @throws IllegalArgumentException
	 * 		When the commands are not valid according isValidInternalCommands.
	 * 		| !isValidInternalCommands(commands)
	 * @throws IllegalArgumentException
	 * 		When the condition is not valid, specified by isValidCondition.
	 * 		| !isValidCondition(condition)
	 * @throws IllegalArgumentException
	 * 		When the falseCommands are not valid.
	 * 		| !isValidFalseCommands(falseCommands)
	 */
	public IfCommand(Condition condition, List<? extends Command> commands, List<? extends Command> falseCommands) {
		super(condition, commands);
		if(!isValidFalseCommands(falseCommands))
			throw new IllegalArgumentException("The given collection of false commands is not valid: " + falseCommands.toString());
		this.falseCommands.addAll(falseCommands);
	}
	
	/**
	 * Returns a List of internal false commands of this.
	 */
	@Basic @Raw @Immutable
	public List<Command> getFalseCommands() {
		return Collections.unmodifiableList(falseCommands);
	}
	
	/**
	 * Controls if the given List of commands is valid.
	 * 
	 * @param falseCommands The list of falseCommands to be controlled.
	 * @return True if every command in falseCommands is different from null and is an instance
	 * of a flowcommand or an executionCommand. Otherwise false.
	 */
	@Raw
	public static boolean isValidFalseCommands(List<? extends Command> falseCommands) {
		return isValidInteralCommands(falseCommands);
	}
	
	/**
	 * Controls if the false commands of this are valid.
	 * 
	 * @return True if every command in getFalseCommands() is different from null and is an instance
	 * of a flowcommand or an executionCommand. Otherwise false.
	 */
	@Raw
	public boolean hasProperFalseCommands() {
		for(Command lusCommand : getFalseCommands()) {
			if(lusCommand == null)
				return false;
			else if(!FlowCommand.class.isInstance(lusCommand) && !ExecutionCommand.class.isInstance(lusCommand))
				return false;
		}
		return true;
	}
	
	/**
	 * A List containing all the arguments of this that must be executed when
	 * the condition of this evaluates to false.
	 */
	private final List<Command> falseCommands = new ArrayList<Command>();

	/**
	 * Executes this FlowCommand by executing all the interal commands if the condition of this
	 * evaluates to true otherwise the false internal commands. In either case, the robot must
	 * be valid.
	 * 
	 * @param robot The robot that must be manipulated.
	 * @effect Every command in getInteralCommands() will be executed, in the specified order if 
	 * the internal condition of this evaluates to true. Otherwise the internal false commands
	 * will be executed.
	 * @throws IllegalArgumentException
	 * 		When one or more internal commands can not be executed with the given robot as
	 * 		argument.
	 */
	@Override
	public void execute(Robot robot) throws IllegalArgumentException {
		super.execute(robot); //execute the commands in arguments, the first commands if the condition is valid.
		if(!this.getCondition().getReturnValue())
			for(Command lusCommand : falseCommands)
				lusCommand.execute(robot);//execute the commands in falseCommands, the second commands.
	}
	
	/**
	 * Search for and executes the (minimumSteps + 1) th basic command (execution command)
	 * The currentSteps is a counter that counts how many basic commands we already passed.
	 * When currentStep is equal to minimumSteps, that basic command will be executed.
	 * 
	 * @param minimumSteps The amount of basic commands we already have executed. 
	 * @param currentSteps The amount of basic commands we already passed.
	 * @param robot The robot that will be manipulated by the given program.
	 * @effect If the condition of this command evaluates to true, then for each interalCommand,
	 *  we will invoke the next method again. But when 
	 * currentSteps become greater then minimumSteps, no more must happen and the method
	 * jumps out of the loop. Else (the condition of this evaluates to false) then the same mechanism happens,
	 * but for the false internal commands.
	 * @return The new currentSteps which will increase when a basic command is found.
	 * @throws IllegalArgumentException
	 * 		When the robot is not valid.
	 * @note The return value is not always equal to the amount of basic commands of this command
	 * added by the start value of it. This is only the case if the result is smaller then
	 * minimumSteps. Otherwise it doesn't care because the stop condition of the recursive
	 * structure of the methods is satisfied.
	 */
	@Override
	public int next(int minimumSteps, int currentSteps, Robot robot) throws IllegalArgumentException {
		this.getCondition().execute(robot);
		if(this.getCondition().getReturnValue())
			return super.next(minimumSteps, currentSteps, robot);
		else {
			for(Command lusCommand : getFalseCommands()) { 
				currentSteps = lusCommand.next(minimumSteps, currentSteps, robot);
				if(currentSteps > minimumSteps)
					break;
			}
			return currentSteps;
		}
	}

	/**
	 * Returns a String representation of this IfCommand.
	 * 
	 * @param leftSpaces The amount of left spaces this if command must
	 * be added to each new line.
	 * @return A String containing this if word and the condition. Then the interal commands
	 * that must be executed when true are added, each on one line. Then an empty line is added.
	 * Finally the internal commands when the condition is false are added on one line. Also, 
	 * the necessary brackets are inserted where needed.
	 */
	@Override
	public String write(String leftSpaces) {
		String write = new String();
		write = leftSpaces + "(" + IF; 
		write = write + " " + this.getCondition().write("") + "\n";
		write = write + super.write(leftSpaces) + "\n";
		
		for(Command command : this.getFalseCommands())
			write = write + command.write(leftSpaces + TextManipulation.LEFT_SPACE);
		
		write = write + leftSpaces + ")\n";
		return write;
	}
	
	/**
	 * Returns the textual source code of this type of command.
	 */
	@Immutable @Basic @Raw
	public static final String getWord() {
		return IF;
	}
	
	/**
	 * The source code word of the IfCommand.
	 */
	private static final String IF  = "if";

}
